﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Phoenix.Xna.Framework.FrameworkExtensions;
using Phoenix.Xna.Framework.Properties;

namespace Phoenix.Xna.Framework.Components.Cameras
{
    using System;

    public abstract class Camera : Component, ICamera
    {
        protected const float DefaultNearClip = 0.1f;
        protected const float DefaultFarClip = 10000.0f;
        protected const float DefaultFOV = 60f;
        protected const float MinFOV = float.Epsilon;
        protected const float MaxFOV = 175.0f;
        protected const int MinZoom = 1;
        protected const int MaxZoom = 32;
        protected const float Phi = MathHelper.PiOver2;
        private Vector3 _eulerRotation;
        private float _radius;
        private float _theta;
        private float _fov;
        private float _nearClip;
        private int _zoomLevel;
        private Vector3 _target;
        private Vector3 _position;

        public static ICamera ActiveCamera { get; set; }

        public Camera()
            : base()
        {
            Initialize(DefaultFOV, DefaultNearClip, DefaultFarClip);
        }

        public Camera(string id)
            : base(id)
        {
            Initialize(DefaultFOV, DefaultNearClip, DefaultFarClip);
        }

        public Camera(string id, float fov, float nearClip, float farClip)
            : base(id)
        {
            Initialize(fov, nearClip, farClip);
        }

        protected virtual void UpdatePosition(float delta, Vector3 rotation)
        {
            Position += delta * Vector3.Transform(rotation, Rotation);
        }

        private void Initialize(float fov, float nearClip, float farClip)
        {
            FOV = fov;
            ZoomLevel = (int)(DefaultFOV / fov);
            NearClip = nearClip;
            FarClip = farClip;
            Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(fov), Viewport.AspectRatio, NearClip, FarClip);
            ZoomLevel = 1;
            RotationDelta = 0.005f;
            MoveDelta = 2f;
            ActiveCamera = this;
        }

        private void UpdateRadius()
        {
            Vector3 difference = _position - _target;
            difference *= difference;
            _radius = (float)Math.Sqrt(difference.X + difference.Z);
            _theta = (_radius * MathHelper.Pi) / 2.0f;
        }

        #region ICamera Members

        public float FOV
        {
            get { return _fov; }
            set
            {
                if (value < float.Epsilon || value > MaxFOV)
                {
                    throw new ArgumentOutOfRangeException(string.Format(Resources.ArgumentOutOfRangeException, "FOV", MinFOV, MaxFOV));
                }

                _fov = value;
            }
        }
        public float NearClip
        {
            get { return _nearClip; }
            set
            {
                if (value <= 0)
                {
                    throw new ArgumentOutOfRangeException(string.Format(Resources.ArgumentOutOfRangeException, "NearClip", 0, "inifinity"));
                }

                _nearClip = value;
            }
        }
        public float FarClip { get; set; }
        public BoundingFrustum Frustum { get { return new BoundingFrustum(View * Projection); } }
        public Matrix View { get; protected set; }
        public Matrix Projection { get; protected set; }
        public Matrix ViewProjection { get { return View * Projection; } }
        public Viewport Viewport { get { return Engine.Instance.GraphicsDevice.Viewport; } }
        public int ZoomLevel
        {
            get { return _zoomLevel; }
            set
            {
                if (value < MinZoom || value > MaxZoom)
                {
                    throw new ArgumentOutOfRangeException(string.Format(Resources.ArgumentOutOfRangeException, "ZoomLevel", MinZoom, MaxZoom));
                }

                _zoomLevel = value;
            }
        }
        public Vector3 Target
        {
            get { return _target; }
            set
            {
                if (_target != value)
                {
                    _target = value;
                    UpdateRadius();
                }
            }
        }

        public void LookAt()
        {
            LookAt(Target);
        }

        public void LookAt(Vector3 target)
        {
            Target = target;
            Rotation = Matrix.CreateWorld(Position, target - Position, Vector3.Up);
            EulerRotation = Rotation.ToEulerAngle();
        }

        public void Activate()
        {
            ActiveCamera = this;
        }

        public void ZoomIn()
        {
            if (ZoomLevel < MaxZoom)
            {
                ZoomLevel *= 2;
            }

            FOV = DefaultFOV / ZoomLevel;
        }

        public void ZoomOut()
        {
            if (ZoomLevel > 1)
            {
                ZoomLevel /= 2;

                FOV = DefaultFOV / ZoomLevel;
            }
        }

        #endregion

        #region IComponent Members

        public override void Update(GameTime gameTime)
        {
            Forward = Rotation.Forward;
            Up = Rotation.Up;
            Right = Rotation.Right;
            Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(FOV), Viewport.AspectRatio, NearClip, FarClip);
            View = Matrix.CreateLookAt(Position, Position + Forward, Vector3.Up);
        }

        #endregion

        #region I3DComponent Members

        public Vector3 Forward { get; protected set; }
        public Vector3 Up { get; protected set; }
        public Vector3 Right { get; protected set; }
        public Vector3 Position { get; set; }
        public Vector2 Scale { get; set; }
        public Vector3 EulerRotation
        {
            get { return _eulerRotation; }
            set
            {
                _eulerRotation = value;
                Rotation = Matrix.CreateFromYawPitchRoll(_eulerRotation.X, _eulerRotation.Y, _eulerRotation.Z);
            }
        }
        public Matrix Rotation { get; protected set; }
        public BoundingBox BoundingBox { get; protected set; }

        public void SetPosition(float? x, float? y, float? z)
        {
            float positionX = (x == null) ? Position.X : x.Value;
            float positionY = (y == null) ? Position.Y : y.Value;
            float positionZ = (x == null) ? Position.Z : z.Value;
            Position = new Vector3(positionX, positionY, positionZ);
        }

        public void SetEulerRotation(float? yaw, float? pitch, float? roll)
        {
            float x = (yaw == null) ? EulerRotation.X : yaw.Value;
            float y = (pitch == null) ? EulerRotation.Y : pitch.Value;
            float z = (roll == null) ? EulerRotation.Z : roll.Value;
            EulerRotation = new Vector3(x, y, z);
        }

        #endregion

        #region I3DMoveable
        public float RotationDelta { get; set; }
        public float MoveDelta { get; set; }
        public float Yaw
        {
            get { return EulerRotation.X; }
            set { SetEulerRotation(value, null, null); }
        }
        public float Pitch
        {
            get { return EulerRotation.Y; }
            set { SetEulerRotation(null, value, null); }
        }
        public float Roll
        {
            get { return EulerRotation.Z; }
            set { SetEulerRotation(null, null, value); }
        }

        public void RotateAroundTarget(float delta)
        {
            _theta += delta;
            float x;
            float y;
            float z;
            x = _radius * (float)(Math.Cos(_theta) * Math.Sin(Phi));
            y = _radius * (float)Math.Cos(Phi);
            z = _radius * (float)(Math.Sin(_theta) * Math.Sin(Phi));
            _position = new Vector3(x, _position.Y, z);
            LookAt();
        }

        public void MoveForward()
        {
            MoveForward(MoveDelta);
        }

        public void MoveForward(float delta)
        {
            UpdatePosition(delta, Vector3.Forward);
        }

        public void MoveBackward()
        {
            MoveBackward(MoveDelta);
        }

        public void MoveBackward(float delta)
        {
            UpdatePosition(delta, -Vector3.Forward);
        }

        public void MoveLeft()
        {
            MoveLeft(MoveDelta);
        }

        public void MoveLeft(float delta)
        {
            UpdatePosition(delta, -Vector3.Right);
        }

        public void MoveRight()
        {
            MoveRight(MoveDelta);
        }

        public void MoveRight(float delta)
        {
            UpdatePosition(delta, Vector3.Right);
        }

        public void MoveUp()
        {
            MoveUp(MoveDelta);
        }

        public void MoveUp(float delta)
        {
            UpdatePosition(delta, Vector3.Up);
        }

        public void MoveDown()
        {
            MoveDown(MoveDelta);
        }

        public void MoveDown(float delta)
        {
            UpdatePosition(delta, -Vector3.Up);
        }

        public void YawLeft()
        {
        }

        public void YawLeft(float delta)
        {
        }

        public void YawRight()
        {
        }

        public void YawRight(float delta)
        {
        }

        public void PitchUp()
        {
        }

        public void PitchUp(float delta)
        {
        }

        public void PitchDown()
        {
        }

        public void PitchDown(float delta)
        {
        }

        public void RollLeft()
        {
        }

        public void RollLeft(float delta)
        {
        }

        public void RollRight()
        {
        }

        public void RollRight(float delta)
        {
        }


        #endregion
    }
}
